﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright company="Collaboris Ltd." file="ActionBase.cs">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Base class for all library actions
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Engine
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;

    
    using Collaboris.Saf.Engine.Report;
    using Collaboris.Saf.Exceptions;
    using Collaboris.Saf.Tracing;
    using Collaboris.Saf.Utilities;
    using Collaboris.Utils;

    using Definitions;

    using SharePoint.Wss.Attributes;
    using SharePoint.Wss.Entities;

    /// <summary>
    /// Base class for all library actions
    /// </summary>
    /// <typeparam name="T">
    /// The Data Entity for the Action
    /// </typeparam>
    public abstract class ActionBase<T> : IAction
        where T : EntityBase
    {
        #region Constants and Fields

        /// <summary>
        /// auto un do.
        /// </summary>
        // private bool autoUnDo;
        /// <summary>
        /// disable do.
        /// </summary>
        private bool disableDo;

        /// <summary>
        /// disable undo.
        /// </summary>
        private bool disableUndo;

        /// <summary>
        /// The do condition.
        /// </summary>
        private bool doCondition = true;

        /// <summary>
        /// entity list.
        /// </summary>
        private EntityBase[] entityList;

        /// <summary>
        /// id.
        /// </summary>
        private Guid id = Guid.Empty;

        /// <summary>
        /// index of the action in the Macro.
        /// </summary>
        private int index;

        /// <summary>
        /// Stores the action internal name
        /// </summary>
        private string internalName;

        /// <summary>
        /// reporter.
        /// </summary>
        // private FileReporter reporter;
        private IMacro macro;

        /// <summary>
        /// Stores the action unique friendly name
        /// </summary>
        private string name;

        /// <summary>
        /// The refresh dynamic variables.
        /// </summary>
        private RefreshDynamicVariablesDelegate refreshDynamicVariables;

        /// <summary>
        /// scope.
        /// </summary>
        private SPScope scope;

        /// <summary>
        /// stop on do exception.
        /// </summary>
        private bool stopOnDoException;

        /// <summary>
        /// stop on undo exception.
        /// </summary>
        private bool stopOnUndoException;

        /// <summary>
        /// trace.
        /// </summary>
        private TraceHelper trace;

        /// <summary>
        /// The undo condition.
        /// </summary>
        private bool undoCondition = true;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ActionBase{T}"/> class. 
        /// Default constructor.
        /// Initializes a new instance of the <see cref="ActionBase&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="actionName">
        /// The action Name.
        /// </param>
        public ActionBase(string actionName)
        {
            this.name = actionName;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ActionBase{T}"/> class. 
        /// Default constructor.
        /// Initializes a new instance of the <see cref="ActionBase&lt;T&gt;"/> class.
        /// </summary>
        public ActionBase()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ActionBase{T}"/> class. 
        /// Initializes a new instance of the <see cref="ActionBase&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="id">
        /// The id to be assigned to this action
        /// </param>
        public ActionBase(Guid id)
        {
            this.id = id;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Defines a SharePoint scope that the Action is 
        /// currently operating within. 
        /// </summary>
        public SPScope Scope
        {
            get
            {
                return this.scope;
            }

            set
            {
                this.scope = value;
            }
        }

        /*
        /// <summary>
        /// Gets or sets Reporter.
        /// </summary>
        public FileReporter Reporter
        {
            get
            {
                if (this.reporter == null)
                {
                    this.reporter = new FileReporter();
                }

                return this.reporter;
            }

            set { this.reporter = value; }
        }        
        */

        /// <summary>
        /// Gets TraceSwitch.
        /// </summary>
        protected static TraceSwitch TraceSwitch
        {
            get
            {
                return ActionsTraceHelper.TraceSwitch;
            }
        }

        /// <summary>
        /// Provides and instance of a Trace Helper
        /// </summary>        
        protected TraceHelper Trace
        {
            get
            {
                if (this.trace == null)
                {
                    this.trace = new ActionsTraceHelper(this);
                }

                return this.trace;
            }
        }

        #endregion

        #region Implemented Interfaces

        #region IAction

        //[SafProperty(
        //  Summary = "Optional. Determines whether to 'Do' or 'Undo' the Action." +
        //            "If the condition evaluates to 'false' then the action will " + 
        //            "be skipped for both the 'Undo' and 'Do'.",
        //  Title   = "Condition",
        //  Remarks = "To author the condition, a Spring Expression is required." +
        //            "Please refer to the Spring documentation " +
        //            "<a href='http://www.springframework.net/doc-latest/reference/html/expressions.html'>here</a> " +
        //            "for more information.",
        //  RenderAsExpression = true,
        //  InputHint = InputHint.Boolean)]
        //
        public bool Condition
        {
            set
            {
                this.DoCondition = value;
                this.UndoCondition = value;
            }
        }

        [CollaborisProperty(
          Summary = "Determines whether to disable the 'Do' on the Action. " +
                    "If 'DisableDo' is set to 'True' then the action will " +
                    "be skipped.",
          Title   = "DisableDo",
          Example = "false",
          InputHint = InputHint.Boolean)]
        public bool DisableDo
        {
            get
            {
                return this.disableDo;
            }

            set
            {
                this.disableDo = value;
            }
        }

        [CollaborisProperty(
         Summary   = "Determines whether to disable the 'Undo' on the Action. " +
                     "If 'DisableUndo' is set to 'True' then the action will " +
                     "be skipped.",
         Title     = "DisableUndo",
         Example   = "false",
         InputHint = InputHint.Boolean)]
        public bool DisableUndo
        {
            get
            {
                return this.disableUndo;
            }

            set
            {
                this.disableUndo = value;
            }
        }

        //[SafProperty(
        //   Summary = "Optional. Determines whether to 'Do' the Action." +
        //             "If the condition evaluates to 'false' then the action will " +
        //             "be skipped for the 'Do'.",
        //   Title   = "DoCondition",
        //   Example = "'${OS}'=='Windows_NT'",
        //   Remarks = "To author the condition, a Spring Expression is required." +
        //             "Please refer to the Spring documentation " +
        //             "<a href='http://www.springframework.net/doc-latest/reference/html/expressions.html'>here</a> " +
        //             "for more information.",
        //   RenderAsExpression = true,
        //   InputHint = InputHint.Boolean)]
        public bool DoCondition
        {
            get
            {
                return this.doCondition;
            }

            set
            {
                this.doCondition = value;
            }
        }

        /// <summary>
        /// Defines a List of POCO's that the Action uses
        /// to performs its task.
        /// </summary>
        [CollaborisProperty(
            Summary =
                "Defines an array of" ,
            Example = "Please refer to the documented types for samples.",
            Remarks = "Entities are the method in which parameters can be supplied to the Action. " +
                      "Many instances of an entity can be passed in. (Please see the XML examples to understand how to configure an entity with values).",
            Title = "Entity List")]
        public EntityBase[] EntityList
        {
            get
            {
                return this.entityList;
            }

            set
            {
                this.entityList = value;
            }
        }
        //public EntityBase[] EntityList
        //{
        //    get
        //    {
        //        return this.entityList;
        //    }

        //    set
        //    {
        //        this.entityList = value;
        //    }
        //}

        [CollaborisProperty(
         Summary = "A unique identifier for the instance of the action. " +
                   "If an ID is not supplied then SAF will create a new one.",
         Title = "ID",
         Required = true,
         Example = "{47B640DD-64A9-493b-8445-789FC0D05FF1}",
         InputHint = InputHint.Guid)]
        public Guid ID
        {
            get
            {
                return this.id;
            }

            set
            {
                this.id = value;
            }
        }

        /// <summary>
        /// Gets the index of the Action in the Macro
        /// specifying it position. 
        /// </summary>
        /// <remarks>The Index is zero based.</remarks>
        public int Index
        {
            get
            {
                return this.index;
            }

            set
            {
                this.index = value;
            }
        }

        /// <summary>
        /// Gets the parent macro.
        /// </summary>
        /// <value>The parent macro.</value>
        public IMacro Macro
        {
            get
            {
                return this.macro;
            }

            set
            {
                this.macro = value;
            }
        }

        /// <summary>
        /// Gets or sets the list of shortcuts supported by this action
        /// </summary>
        // public ShortcutsDictionary Shortcuts
        // {
        // get { return this.shortcuts; }
        // }
        /*
        /// <summary>
        /// Determines whether to automatically call "Undo" on the 
        /// Actions in the Macro if an Exception occurs.
        /// </summary>
        /// <remarks>
        /// <para>
        /// If there are 10 Actions in the macro and the 7th Action
        /// raises an exception (and <see cref="StopOnDoException"/> 
        /// is 'True'), then the UnDo will be called on the 7th Action
        /// then the 6th, 5th, 4th, 3rd, 2nd and finally the first.
        /// </para>
        /// <para>
        /// If an Action raises an Exception in it's UnDo Method, this 
        /// DOESN'T cause Macro Processing to Stop. To Stop further
        /// Macro Processing, the Action needs to return 
        /// <see cref="ProcessMode"/>.Stop.
        /// </para>
        /// <para>
        /// If a seperate "Undo" or "Activation" Macro is specified
        /// in a particular "Adapter", then this is ignored.
        /// </para>
        /// <para>
        /// This will only be used if <see cref="StopOnDoException"/>
        /// is set to 'True';
        /// </para>
        /// </remarks>
        public bool AutoUndo
        {
            get { return this.autoUnDo; }

            set { this.autoUnDo = value; }
        }
        */

        /// <summary>
        /// Gets the Name of the Current Action
        /// </summary>
        public string Name
        {
            get
            {
                if (String.IsNullOrEmpty(this.name))
                {
                    this.name = this.GetType().FullName;
                }

                return this.name;
            }

            set
            {
                this.name = value;
            }
        }

        /// <summary>
        /// Sets RefreshDynamicVariables.
        /// </summary>
        public RefreshDynamicVariablesDelegate RefreshDynamicVariables
        {
            set
            {
                this.refreshDynamicVariables = value;
            }
        }

        [CollaborisProperty(
         Summary = "A bool indicating whether to stop processing " +
                   "if an exception is encountered during the 'Do' operation of the Action.",
         Title = "Stop on Do Exception",
         Example = "true",
         InputHint = InputHint.Boolean)]
        public bool StopOnDoException
        {
            get
            {
                return this.stopOnDoException;
            }

            set
            {
                this.stopOnDoException = value;
            }
        }

        [CollaborisProperty(
         Summary = "A bool indicating whether to stop processing " +
                   "if an exception is encountered during the 'Undo' operation of the Action.",
         Title = "Stop on Undo Exception",
         Example = "true",
         InputHint = InputHint.Boolean)]
        public bool StopOnUndoException
        {
            get
            {
                return this.stopOnUndoException;
            }

            set
            {
                this.stopOnUndoException = value;
            }
        }

        //[SafProperty(
        //  Summary = "Optional. Determines whether to 'Undo' the Action." +
        //            "If the condition evaluates to 'false' then the action will " +
        //            "be skipped for the 'Undo'.",
        //  Title   = "Undo Condition",
        //  Example = "'${UserName}'=='Administrator'",
        //  Remarks = "To author the condition, a Spring Expression is required." +
        //            "Please refer to the Spring documentation " +
        //            "<a href='http://www.springframework.net/doc-latest/reference/html/expressions.html'>here</a> " +
        //            "for more information.",
        //  RenderAsExpression = true,
        //  InputHint = InputHint.Boolean)]
        public bool UndoCondition
        {
            get
            {
                return this.undoCondition;
            }

            set
            {
                this.undoCondition = value;
            }
        }

        /// <summary>
        /// Traces and calls Undo on the Action in the Macro
        /// </summary>
        /// <param name="context">
        /// </param>
        /// <returns>
        /// </returns>
        public IProgressReport Do(SafContext context)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "ActionBase", "Do");

            ActionReport report = this.Run(context, ProcessMode.Do);

            TraceHelper.TraceMethodStart(TraceSwitch, "ActionBase", "Do");
            return report;
        }

        /// <summary>
        /// Traces and calls Undo on the Action in the Macro
        /// </summary>
        /// <param name="context">
        /// </param>
        /// <returns>
        /// </returns>
        public IProgressReport Undo(SafContext context)
        {
            this.Trace.TraceMethodStart("Undo");

            this.Trace.TraceVerbose(" UNDOING ACTION - START");

            ActionReport report = this.Run(context, ProcessMode.Undo);

            this.Trace.TraceVerbose(" UNDOING ACTION - END");

            this.Trace.TraceMethodEnd("Undo");
            return report;
        }

        #endregion

        #endregion

        #region Helper Methods

        /// <summary>
        /// Validates the int for zero.
        /// </summary>
        /// <param name="objectToVal">
        /// The object to val.
        /// </param>
        /// <param name="objectName">
        /// Name of the object.
        /// </param>
        /// <param name="errors">
        /// The errors.
        /// </param>
        protected static void ValidateIntForZero(int objectToVal, string objectName, ref List<ValidationError> errors)
        {
            if (objectToVal == 0)
            {
                errors.Add(
                    new ValidationError(
                        objectName, "'" + objectName + "' is not a number. An number not equal to zero was expected."));
            }
        }

        /// <summary>
        /// Validates the object for null.
        /// </summary>
        /// <param name="objectToVal">
        /// The object to val.
        /// </param>
        /// <param name="objectName">
        /// Name of the object.
        /// </param>
        /// <param name="errors">
        /// The errors.
        /// </param>
        protected static void ValidateObjectForNull(
            object objectToVal, string objectName, ref List<ValidationError> errors)
        {
            if (objectToVal == null)
            {
                errors.Add(
                    new ValidationError(
                        objectName, "'" + objectName + "' is null, (nothing or empty), an object was expected."));
            }
        }

        /// <summary>
        /// Validates the string for null.
        /// </summary>
        /// <param name="objectToVal">
        /// The object to val.
        /// </param>
        /// <param name="objectName">
        /// Name of the object.
        /// </param>
        /// <param name="errors">
        /// The errors.
        /// </param>
        protected static void ValidateStringForNull(
            string objectToVal, string objectName, ref List<ValidationError> errors)
        {
            if (string.IsNullOrEmpty(objectToVal))
            {
                errors.Add(
                    new ValidationError(
                        objectName, "'" + objectName + "' is null, (nothing or empty), as string was expected."));
            }
        }
        
        /// <summary>
        /// Validates the Nullable Type for null.
        /// </summary>
        /// <typeparam name="T">
        /// </typeparam>
        /// <param name="objectToVal">
        /// The object to val.
        /// </param>
        /// <param name="objectName">
        /// Name of the object.
        /// </param>
        /// <param name="errors">
        /// The errors.
        /// </param>
        protected static void ValidateNullable<T>(
            T? objectToVal, string objectName, ref List<ValidationError> errors) where T : struct 
        {
            if (!objectToVal.HasValue)
            {
                errors.Add(
                    new ValidationError(
                        objectName, "'" + objectName + "' is null, (nothing or empty), a value was expected."));
            }
        }

        /// <summary>
        /// Executes the Action using the values passed in by the Data Entity
        /// and the <see cref="SafContext"/>.
        /// </summary>
        /// <param name="context">
        /// A valid <see cref="SafContext"/> containing properties to run the Action with.
        /// </param>
        /// <param name="entity">
        /// A Valid <see cref="T"/> containing the parameters with which to run the Action with.
        /// </param>
        /// <returns>
        /// A <see cref="ProcessStatus"/> signifying the outcome of processing the Action.
        /// </returns>
        protected virtual ProcessStatus DoAction(SafContext context, T entity)
        {
            this.trace.TraceMethodStart("DoAction - Empty Implementation");
            this.trace.TraceMethodEnd("DoAction - Empty Implementation");

            return ProcessStatus.Skip;
        }

        /// <summary>
        /// Performs Activities at the beginning of the Actions "Do"
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        protected virtual void DoActionBegin(SafContext context, T item)
        {
        }

        /// <summary>
        /// Performs Activities after the Action "Do" has ended.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        protected virtual void DoActionEnd(SafContext context, T item)
        {
        }

        /// <summary>
        /// Helper Method for Tracing During the Start of an Undo or Do Action
        /// </summary>
        /// <param name="actionName">
        /// The action Name.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="entity">
        /// The entity.
        /// </param>
        protected void DoTracing(string actionName, ProcessMode mode, T entity)
        {
            // only trace do the serialisation in Verbos
            if (this.Trace.Switch.TraceInfo)
            {
                string xml = SerialisationHelper.Serialize<T>(entity);
                if (!string.IsNullOrEmpty(xml))
                {
                    this.Trace.TraceInfo("Processing this entityList : " + xml);
                }
            }
        }

        /// <summary>
        /// get entity collection.
        /// </summary>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="entityCollection">
        /// The entity collection.
        /// </param>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <returns>
        /// </returns>
        protected virtual List<object> GetEntityCollection(
            ProcessMode mode, EntityBase[] entityCollection, SafContext context)
        {
            this.Trace.TraceMethodStart("GetEntityCollection()");
            var list = new List<object>();

            if (entityCollection == null)
            {
                return list;
            }

            if (mode == ProcessMode.Undo)
            {
                int length = entityCollection.Length;

                // Need to find out how many entities have been processsed by the do
                if (context.LastDoReport != null)
                {
                    // Get the do action report for this action
                    ActionReport lastDoReport = context.LastDoReport.GetActionReport(this.ID, ProcessMode.Do);

                    // If do report exists then lets find out how many entities have been processed
                    if (lastDoReport != null)
                    {
                        length = lastDoReport.EntityReports.Count;
                    }
                }

                if (length > 0)
                {
                    // Copy them in reverse order
                    for (int i = length - 1; i >= 0; i--)
                    {
                        EntityBase entity = entityCollection[i];
                        entity.Index = i;
                        list.Add(entityCollection[i]);
                    }
                }
            }
            else
            {
                for (int i = 0; i < entityCollection.Length; i++)
                {
                    EntityBase entity = entityCollection[i];
                    entity.Index = i;
                    list.Add(entityCollection[i]);
                }
            }

            this.Trace.TraceMethodEnd("GetEntityCollection()");
            return list;
        }

        /// <summary>
        /// Generates a unique Key for the Action and Entity.
        /// </summary>
        /// <param name="entity">
        /// The entity.
        /// </param>
        /// <returns>
        /// The get unique key.
        /// </returns>
        protected virtual string GetUniqueKey(EntityBase entity)
        {
            return this.Name + "_" + entity.Index;
        }

        /// <summary>
        /// Checks the Data entity used by the Action for Validity
        /// </summary>
        /// <param name="context">
        /// The <see cref="SafContext"/> passed in by the SAF Engine.
        /// </param>
        /// <param name="entity">
        /// The data entity to Validate.
        /// </param>
        /// <param name="errors">
        /// List of <see cref="ValidationError"/>s
        /// </param>
        /// <param name="mode">
        /// <see cref="ProcessMode"/>signalling wheter we are Doing, or Undoing the action.
        /// </param>
        /// <returns>
        /// false if invalid; otherwise true.
        /// </returns>
        protected virtual bool IsValid(SafContext context, T entity, ref List<ValidationError> errors, ProcessMode mode)
        {
            this.Trace.TraceMethodStart("IsValid - Empty Implementation");

            this.Trace.TraceMethodEnd("IsValid - Empty Implementation");

            return true;
        }

        /// <summary>
        /// The refresh variables.
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        protected void RefreshVariables(object instance)
        {
            if (this.refreshDynamicVariables != null)
            {
                this.refreshDynamicVariables(instance);
            }
        }

        /// <summary>
        /// Undoes the action by attempting to roll back what was done in the Do.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="entity">
        /// The data entityList.
        /// </param>
        /// <returns>
        /// A <see cref="ProcessStatus"/> indicating the outcome of the Undo on the Action.
        /// </returns>
        protected virtual ProcessStatus UndoAction(SafContext context, T entity)
        {
            this.trace.TraceMethodStart("UndoAction - Empty Implementation");
            this.trace.TraceMethodEnd("UndoAction - Empty Implementation");

            return ProcessStatus.Skip;
        }

        /// <summary>
        /// Performs Activities at the beginning of the Actions "Undo"
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        protected virtual void UndoActionBegin(SafContext context, T item)
        {
        }

        /// <summary>
        /// Performs Activities at the end of the Actions "Undo"
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        protected virtual void UndoActionEnd(SafContext context, T item)
        {
        }

        /// <summary>
        /// Calls the "do" or "undo" on the action.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="status">
        /// The status.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <param name="report">
        /// The report.
        /// </param>
        /// <returns>
        /// </returns>
        private ProcessStatus CallDoUndoOnAction(
            SafContext context, ProcessMode mode, ProcessStatus status, T item, EntityReport report)
        {
            this.Trace.TraceMethodStart("CallDoUndoOnAction()");
            try
            {
                if (mode == ProcessMode.Undo)
                {
                    try
                    {
                        this.UndoActionBegin(context, item);

                        status = this.UndoAction(context, item);
                    }
                    finally
                    {
                        this.UndoActionEnd(context, item);
                    }
                }
                else
                {
                    try
                    {
                        this.DoActionBegin(context, item);

                        status = this.DoAction(context, item);
                    }
                    finally
                    {
                        this.DoActionEnd(context, item);
                    }
                }

                // Process the Propeties
                // ActionReflector.ProcessProperties(this, context);
            }
            catch (Exception ex)
            {
                this.Trace.TraceException(string.Format("{0}Action failed! ", mode), ex);
                throw;
            }

            this.Trace.TraceMethodEnd("CallDoUndoOnAction()");
            return status;
        }

        /// <summary>
        /// Decides whether to continue or not. Anything greater that a
        /// ProcessStatus.Warning will return false and TraceInfo
        /// </summary>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="status">
        /// The status.
        /// </param>
        /// <returns>
        /// The continue with action.
        /// </returns>
        private bool ContinueWithAction(ProcessMode mode, ProcessStatus status)
        {
            this.Trace.TraceMethodStart("ContinueWithAction");
            if (status > ProcessStatus.Warning)
            {
                // ok we have received a Stop or Error
                this.Trace.TraceInfo(
                    "Processing of the EntityList has Stopped in the '{1}' Status Code : {0}", 
                    status.ToString(), 
                    mode.ToString());

                this.Trace.TraceMethodEnd("ContinueWithAction");

                return false;
            }

            this.Trace.TraceMethodEnd("ContinueWithAction");
            return true;
        }

        /// <summary>
        /// Determines whether the specified mode is disabled.
        /// </summary>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <returns>
        /// <c>true</c>if the specified mode is disabled; otherwise, <c>false</c>.
        /// </returns>
        private bool IsDisabled(ProcessMode mode)
        {
            bool disabled;
            if (mode == ProcessMode.Undo)
            {
                disabled = this.DisableUndo;
            }
            else
            {
                disabled = this.DisableDo;
            }

            return disabled;
        }

        /// <summary>
        /// Processes the actions.
        /// Note. This is called by either :
        /// - public ActionReport Undo(IContext context)
        /// - public ActionReport Do(IContext context)
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <returns>
        /// </returns>
        private ActionReport Run(SafContext context, ProcessMode mode)
        {
            // TraceHelper.TraceMethodStart(TraceSwitch, "ActionBase", "Run");
            this.Trace.TraceMethodStart("Run()");
            this.Trace.TraceVerbose("Processing this action '{0}' for this mode '{1}'", this.Name, mode.ToString());

            var actionReport = ProgressReportUtils.StartReport<ActionReport>(mode, this.Name, this.ID);
            actionReport.ActionIndex = this.Index;

            bool disabled = this.IsDisabled(mode);
            bool stopOnException = this.StopWithException(mode);

            ProcessStatus status = ProcessStatus.Success;
            try
            {
                if (!disabled)
                {
                    bool condition = true;
                    if (mode == ProcessMode.Do)
                    {
                        condition = this.DoCondition;
                    }

                    if (mode == ProcessMode.Undo)
                    {
                        condition = this.UndoCondition;
                    }

                    // evaluate doCondition
                    // if (this.EvaluateConditionOnAction(conditionalString) == false)
                    if (condition == false)
                    {
                        this.Trace.TraceWarning(
                            "The 'Condition' applied to this action '{0}' was evaluated and returned false. This Action will be skipped.", 
                            this.Name);

                        status = ProcessStatus.Skip;
                    }
                    else
                    {
                        // update any variables before running the action
                        this.RefreshVariables(this);

                        if (this.EntityList == null)
                        {
                            // ok the Action doesnt have any entities so lets run her
                            status = this.RunActionWithNoEntity(context, mode, status);
                        }
                        else
                        {
                            // this Action does have entities, so lets loop through and run em all.                        
                            status = this.RunActionWithEntities(context, mode, actionReport, stopOnException, status, 0);
                        }

                        // update any variables after running the action
                        this.RefreshVariables(this);
                    }
                }
                else
                {
                    status = ProcessStatus.Skip;
                    this.Trace.TraceInfo(
                        "Skipping the '{0}Action()' method as 'Disable{0}' Property is set to 'True'", mode.ToString());
                }
            }
            catch (EntityRunException entRunEx)
            {
                // An Error has occurred in an Action that has NO entities!
                status = ProcessStatus.Error;

                // Add the Entity Report in
                // actionReport.EntityReports.Add((EntityReport)entRunEx.ProgressReport);
                throw new ActionRunException(
                    "Processing the Action failed." + " Examine the Inner Exceptions for more detail.", 
                    entRunEx, 
                    actionReport);
            }
            catch (ActionNoEntityRunException actNoEntEx)
            {
                // An Error has occurred in an Action with Entitys
                status = ProcessStatus.Error;
                throw new ActionRunException(
                    "Processing the Action failed." + " Examine the Inner Exceptions for more detail.", 
                    actNoEntEx, 
                    actionReport);
            }
            catch (Exception exception)
            {
                // An Error has occurred in an Action with Entitys
                status = ProcessStatus.Error;
                throw new ActionRunException(
                    "Processing the Action failed." + " Examine the Inner Exceptions for more detail.", 
                    exception, 
                    actionReport);
            }
            finally
            {
                this.Trace.TraceVerbose("Run(): Finished - status returned : {0}", status);

                ProgressReportUtils.FinishReport<ActionReport>(actionReport, status);
                this.Trace.TraceMethodEnd("Run()");
            }

            return actionReport;
        }

        /// <summary>
        /// Loops through the entites on the Action and calls Do or UnDo
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="actionReport">
        /// The action report.
        /// </param>
        /// <param name="stopOnException">
        /// if set to <c>true</c> [stop on exception].
        /// </param>
        /// <param name="status">
        /// The status.
        /// </param>
        /// <param name="macroPos">
        /// The macro Pos.
        /// </param>
        /// <returns>
        /// </returns>
        private ProcessStatus RunActionWithEntities(
            SafContext context, 
            ProcessMode mode, 
            ActionReport actionReport, 
            bool stopOnException, 
            ProcessStatus status, 
            int macroPos)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "ActionBase", "RunActionWithEntities");

            List<object> entitiesCollection = this.GetEntityCollection(mode, this.EntityList, context);

            ProcessStatus currentStatus = status;

            // bool exception = false;

            // Loop through the entities 
            // foreach (T item in entitiesCollection)
            // Start at the beginning of the macro and increment
            int entityIdx;
            for (entityIdx = macroPos; entityIdx < entitiesCollection.Count; entityIdx++)
            {
                object item = entitiesCollection[entityIdx];

                var entityReport = ProgressReportUtils.StartReport<EntityReport>(mode, this.Name, this.ID);

                entityReport.Entity = item;

                try
                {
                    this.RefreshVariables(item);

                    // This will either thrown an Exception from the 
                    // Action itself, or a ValidationHelper Error.
                    currentStatus = this.ValidateAndRun(context, mode, status, item as T, entityReport);

                    // Decide whether the status returned is more than 
                    // a warning (it will be logged already)
                    if (!this.ContinueWithAction(mode, currentStatus))
                    {
                        break;
                    }
                }
                catch (Exception ex)
                {
                    currentStatus = ProcessStatus.Error;

                    // exception = true;

                    if (stopOnException)
                    {
                        string errorMessage =
                            string.Format(
                                "Processing of the EntityList has Stopped in the '{1}' Status Code : {0}", status, mode);
                        // ok we have received a Stop or Error
                        this.Trace.TraceError(errorMessage);
                        throw new EntityRunException(errorMessage, ex, entityReport);
                    }
                    else
                    {
                        // Stop on exception is false so we must log the exception and proceed
                        this.Trace.TraceException("VerifyThrowException()", ex);
                    }
                }
                finally
                {
                    entityReport = ProgressReportUtils.FinishReport<EntityReport>(entityReport, currentStatus);
                    actionReport.EntityReports.Add(entityReport);

                    TraceHelper.TraceMethodEnd(TraceSwitch, "ActionBase", "RunActionWithEntities");

                    /*
                    if (exception)
                        entityIdx = this.RollBackAction(context, mode, actionReport, entitiesCollection, entityIdx);                    
                    */
                }
            }

            return currentStatus;
        }

        /*
        /// <summary>
        /// Rolls the back action.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="actionReport">
        /// The action report.
        /// </param>
        /// <param name="entitiesCollection">
        /// The entities collection.
        /// </param>
        /// <param name="macroIndex">
        /// Index of the macro.
        /// </param>
        /// <returns>
        /// The roll back action.
        /// </returns>
        private int RollBackAction(
                SafContext context,
                ProcessMode mode,
                ActionReport actionReport,
                List<Object> entitiesCollection,
                int macroIndex)
        {
            if (this.AutoUndo && mode == ProcessMode.Do)
            {
                this.Trace.TraceWarning(
                        "'AutoUndo' has been set on the macro, so the Entities passed will be " + "'UnDone'"
                        + " method will be called in reverse order, starting with the "
                        + "action that caused the Exception.",
                        macroIndex.ToString());

                macroIndex = (entitiesCollection.Count - 1) - macroIndex;

                // Try and Auto Undo then
                // UndoMacro(context, macroIndex, macroReport, true);
                this.RunActionWithEntities(
                        context,
                        ProcessMode.Undo,
                        actionReport,
                        this.StopWithException(ProcessMode.Undo),
                        ProcessStatus.Error,
                        macroIndex);
            }

            return macroIndex;
        }
        */

        /// <summary>
        /// Runs the action has it has not entity
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="status">
        /// The status.
        /// </param>
        /// <returns>
        /// </returns>
        private ProcessStatus RunActionWithNoEntity(SafContext context, ProcessMode mode, ProcessStatus status)
        {
            this.Trace.TraceMethodStart("RunActionWithNoEntity");

            ProcessStatus currentStatus;

            try
            {
                // Theres NO Data EntityList so lets call in and pass a null
                currentStatus = this.ValidateAndRun(context, mode, status, default(T), null);
            }
            catch (Exception ex)
            {
                this.Trace.TraceMethodEnd("RunActionWithNoEntity");

                // Lets throw and let the next handler deal with it.
                throw new ActionNoEntityRunException(
                    "An action (containing no entities) has failed " + "- please examine the inner exception.", ex);
            }

            this.Trace.TraceMethodEnd("RunActionWithNoEntity");
            return currentStatus;
        }

        /// <summary>
        /// Stops the with exception.
        /// </summary>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <returns>
        /// The stop with exception.
        /// </returns>
        private bool StopWithException(ProcessMode mode)
        {
            bool stop;
            if (mode == ProcessMode.Undo)
            {
                stop = this.StopOnUndoException;
            }
            else
            {
                stop = this.StopOnDoException;
            }

            return stop;
        }

        /// <summary>
        /// Traces the validation errors.
        /// </summary>
        /// <param name="validationErrors">
        /// The validation errors.
        /// </param>
        private void TraceValidationErrors(List<ValidationError> validationErrors)
        {
            this.Trace.TraceMethodStart("TraceValidationErrors");

            if (validationErrors == null)
            {
                return;
            }

            foreach (ValidationError error in validationErrors)
            {
                string msg = String.Format(
                    "VALIDATION ERRROR - Property : '{0}' Message '{1}'", error.EntityProperty, error.ValidationMsg);

                this.Trace.TraceWarning(msg);
            }

            this.Trace.TraceMethodEnd("TraceValidationErrors");
        }

        /// <summary>
        /// Validates the data entity and then runs the action if ok
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="status">
        /// The status.
        /// </param>
        /// <param name="item">
        /// The item.
        /// </param>
        /// <param name="entityReport">
        /// The entity Report.
        /// </param>
        /// <returns>
        /// Will return ProcessStatus.Invalid
        /// </returns>
        private ProcessStatus ValidateAndRun(
            SafContext context, ProcessMode mode, ProcessStatus status, T item, EntityReport entityReport)
        {
            this.Trace.TraceMethodStart("ValidateAndRun");
            status = ProcessStatus.Success;
            var validationErrors = new List<ValidationError>();

            this.Trace.TraceVerbose("{0}Action(): Starting ValidationHelper on {1}", mode, this.Name);

            bool isValid = this.IsValid(context, item, ref validationErrors, mode);

            this.Trace.TraceVerbose("{0}Action(): Ending ValidationHelper {1}", mode, this.Name);

            // If we are valid lets call
            if (isValid)
            {
                this.Trace.TraceInfo("Started  - {0} ({1})", this.Name, mode);

                status = this.CallDoUndoOnAction(context, mode, status, item, entityReport);

                this.Trace.TraceInfo("Finished - {0} ({1}) ({2})", this.Name, mode, status);
            }
            else
            {
                this.Trace.TraceError(
                    "The Action is not valid, please examine the ValidationHelper errors in the exception.");
                this.Trace.TraceMethodEnd("ValidateAndRun");

                this.TraceValidationErrors(validationErrors);

                throw new ValidationException(
                    "The Action is not valid, please amend the values on the properties listed above.", validationErrors);
            }

            this.Trace.TraceMethodEnd("ValidateAndRun");
            return status;
        }

        #endregion

        /// <summary>
        /// Gets the class definition.
        /// </summary>
        /// <value>The class definition.</value>
        public SafClassDefinition Definition
        {
            get
            {
                SafClassDefinition safClassDefinition = SafDefinitionCache.SafClassDefinitions.Find(this.GetType());

                if (safClassDefinition != null)
                    return safClassDefinition;

                return null;
            }
        }
    }
}